// the size of the framebuffer
float2 fbDim;

float4x4 invWVP;
float4x4 invView;

float3 eyePosition;

float4 lightColor;
float3 lightDirection;
float3 lightPosition;
float cosCutOff; // cosine of spotlight cutoff angle

// positional light falloff coefficients
float lightC;
float lightL;
float lightQ;

// fog falloff coefficients
float fogC;
float fogL;
float fogQ;

texture2D color;
sampler2D colorSampler = sampler_state
{
	Texture = <color>;
	magfilter = POINT;
	minfilter = POINT;
	mipfilter = POINT;
	AddressU = clamp;
	AddressV = clamp;
};

texture2D normal;
sampler2D normalSampler = sampler_state
{
	Texture = <normal>;
	magfilter = POINT;
	minfilter = POINT;
	mipfilter = POINT;
	AddressU = clamp;
	AddressV = clamp;
};

texture2D depth;
sampler2D depthSampler = sampler_state
{
	Texture = <depth>;
	magfilter = POINT;
	minfilter = POINT;
	mipfilter = POINT;
	AddressU = clamp;
	AddressV = clamp;
};


struct VSInput
{
	float3 Position : POSITION0;
	float2 Texture  : TEXCOORD0;
	float2 VPos : VPOS;
};

struct VSOutput
{
	float4 Position : POSITION0;
	float2 Texture  : TEXCOORD0;
};

struct PSInput
{
	float2 Texture : TEXCOORD0;
};

VSOutput VSFunction(VSInput input)
{
	VSOutput output;

	output.Position = float4(input.Position,1);

	output.Texture = input.Texture;

	return output;
}

float3 getPosition(float2 screenCoord, float depth) {
	float4 position = mul(float4((screenCoord.x * 2 - 1), ((1 - screenCoord.y) * 2 - 1), depth, 1), invWVP);
	position.xyz /= position.w;
	position.w = 1;
	return position;
}


float4 PSAmbient(PSInput input) : COLOR0
{
	float4 color = tex2D(colorSampler, input.Texture);
	color.w = 1;
	return color * float4(lightColor);
}


float4 PSDirectional(PSInput input) : COLOR0 
{
	float4 color = tex2D(colorSampler, input.Texture);
	float3 normal = (tex2D(normalSampler, input.Texture).xyz - float3(0.5, 0.5, 0.5)) * 2;
	float depth = tex2D(depthSampler, input.Texture);

	float3 position = getPosition(input.Texture, depth);

	//Diffuse
	float3 L = normalize(-lightDirection);
	float diffuseLight = max(dot(normalize(normal), L), 0);
	float4 diffuse = lightColor * diffuseLight;

	//Specular
	float3 V = normalize(eyePosition - position);
	float3 H = normalize(L + V);
	// float specularLight = pow(max(dot(normal, H),0),specularPower);
	float specularLight = pow(max(dot(normal, H), 0), 2);
	if(diffuseLight <= 0) 
		specularLight=0;
	float4 specular = lightColor * specularLight;

	float4 light = diffuse + specular;

	return color * light;
}

float4 PSPoint(PSInput input) : COLOR0
{
	float4 color = tex2D(colorSampler, input.Texture);
	float3 normal = (tex2D(normalSampler, input.Texture).xyz - float3(0.5, 0.5, 0.5)) * 2;
	float depth = tex2D(depthSampler, input.Texture).x;

	float3 position = getPosition(input.Texture, depth);

	//Diffuse
	float3 L = -normalize(lightPosition - position);
	float diffuseLight = max(dot(normalize(normal), L), 0);
	float4 diffuse = lightColor * diffuseLight;

	//Specular
	float3 V = normalize(eyePosition - position);
	float3 H = normalize(L + V);
	// float specularLight = pow(max(dot(normal, H),0),specularPower);
	float specularLight = max(dot(normal, H), 0);
	if(diffuseLight <= 0)
		specularLight = 0;
	float4 specular = lightColor * specularLight;

	float dist = length(position - lightPosition);
	diffuse = 1;
	specular = 0;
	float4 light = (diffuse + specular) / (lightC + dist * lightL + dist * dist * lightQ);

	return color * light * lightColor;
}

float4 PSSpot(PSInput input) : COLOR0
{
	float4 color = tex2D(colorSampler, input.Texture);
	float3 normal = (tex2D(normalSampler, input.Texture).xyz - float3(0.5, 0.5, 0.5)) * 2;
	float depth = tex2D(depthSampler, input.Texture).x;

	float3 position = getPosition(input.Texture, depth);


	//Diffuse
	float3 L = -normalize(lightPosition - position);
	float diffuseLight = max(dot(normalize(normal), L), 0);
	float4 diffuse = lightColor * diffuseLight;

	//Specular
	float3 V = normalize(eyePosition - position);
	float3 H = normalize(L + V);
	// float specularLight = pow(max(dot(normal, H),0),specularPower);
	float specularLight = max(dot(normal, H), 0);
	if(diffuseLight <= 0)
		specularLight=0;
	float4 specular = lightColor * specularLight;

	//Spotlight luminance
	float lum = clamp(pow((dot(L, lightDirection) - cosCutOff)/cosCutOff, 2), 0, 1);

	float dist = length(position - lightPosition);
	diffuse = 1;
	specular = 0;
	float4 light = (diffuse + specular) / (lightC + dist * lightL + dist * dist * lightQ);

	return color * light * lightColor * lum;
}

float4 PSFog(PSInput input) : COLOR0
{
	float zdist = tex2D(depthSampler, input.Texture);
	float4 color = tex2D(colorSampler, input.Texture);
	float fog = 1 / (fogC + fogL * zdist + fogQ * zdist * zdist);
	return color * fog + (1 - fog) * lightColor + float4(0,0,0,1);
}

float4 PSLaplace(PSInput input) : COLOR0
{
	int i; // loop counter

	// the number of pixels around the center to sample depth
	float w = 2;
	// depth samples
	float samples[9] = {0,0,0,0,0,0,0,0,0}; // 9 local depth samples in row-major order
	samples[0] = tex2D(depthSampler, input.Texture + (float2(-w, -w) / fbDim)).x;
	samples[1] = tex2D(depthSampler, input.Texture + (float2( 0, -w) / fbDim)).x;
	samples[2] = tex2D(depthSampler, input.Texture + (float2( w, -w) / fbDim)).x;
	samples[3] = tex2D(depthSampler, input.Texture + (float2(-w,  0) / fbDim)).x;
	samples[4] = tex2D(depthSampler, input.Texture + (float2( 0,  0) / fbDim)).x;
	samples[5] = tex2D(depthSampler, input.Texture + (float2( w,  0) / fbDim)).x;
	samples[6] = tex2D(depthSampler, input.Texture + (float2(-w,  w) / fbDim)).x;
	samples[7] = tex2D(depthSampler, input.Texture + (float2( 0,  w) / fbDim)).x;
	samples[8] = tex2D(depthSampler, input.Texture + (float2( w,  w) / fbDim)).x;
	float centerZ = samples[4];

	float laplaceFilter[9] = {
		-1, -1, -1,
		-1,  8, -1,
		-1, -1, -1
	};
	float laplace = 0;
	for(i = 0; i < 9; i++) {
		laplace += laplaceFilter[i] * samples[i];
	}

	laplace = abs(laplace);
	laplace = clamp(laplace, 0, 1);
	float threshold = 1/5000.0f;
	if(laplace > threshold)
		return lightColor;
	else
		return float4(0,0,0,0);
}

/*
	Suppresses lone non-zero pixels.
*/
float4 PSDenoise(PSInput input) : COLOR0
{
	float EPSILON = 0.0000001;
	int i; // loop counter

	// the number of pixels around the center to sample depth
	float w = 1;
	// depth samples
	float samples[4] = {0,0,0,0};
	// samples[0] = tex2D(colorSampler, input.Texture + (float2(-w, -w) / fbDim)).w;
	samples[0] = tex2D(colorSampler, input.Texture + (float2( 0, -w) / fbDim)).w;
	// samples[2] = tex2D(colorSampler, input.Texture + (float2( w, -w) / fbDim)).w;
	samples[1] = tex2D(colorSampler, input.Texture + (float2(-w,  0) / fbDim)).w;
	// samples[4] = tex2D(colorSampler, input.Texture + (float2( 0,  0) / fbDim)).w;
	samples[2] = tex2D(colorSampler, input.Texture + (float2( w,  0) / fbDim)).w;
	// samples[5] = tex2D(colorSampler, input.Texture + (float2(-w,  w) / fbDim)).w;
	samples[3] = tex2D(colorSampler, input.Texture + (float2( 0,  w) / fbDim)).w;
	// samples[7] = tex2D(colorSampler, input.Texture + (float2( w,  w) / fbDim)).w;

	int num = 0;
	for(i = 0; i < 4; i++) {
		if(samples[i] > EPSILON) {
			num++;
		}
	}

	if(num > 2)
		return tex2D(colorSampler, input.Texture);
	else
		return float4(0,0,0,0);
}

float4 PSBright(PSInput input) : COLOR0
{
	float4 color = tex2D(colorSampler, input.Texture);
	float minBloomColor = 0.35;
	color = (color - float4(minBloomColor, minBloomColor, minBloomColor, 0));
	color += float4(0.03, 0.03, 0.03, 0);
	color /= minBloomColor;
	return clamp(color, float4(0,0,0,1), float4(1,1,1,1));
}

float4 PSBlurH(PSInput input) : COLOR0
{
	int i; // loop counter
	float filter[5] = { 1/20.0f, 3/20.0f, 12/20.0f, 3/20.0f, 1/20.0f};
	float4 total = float4(0, 0, 0, 0);
	for(i = 0; i < 5; i++) {
		float4 sample = tex2D(colorSampler, input.Texture + (float2(i - 2, 0) / fbDim));
		total += filter[i] * sample;
	}
	return total;
}

float4 PSBlurV(PSInput input) : COLOR0
{
	int i; // loop counter
	float filter[5] = { 1/20.0f, 3/20.0f, 12/20.0f, 3/20.0f, 1/20.0f};
	float4 total = float4(0, 0, 0, 0);
	for(i = 0; i < 5; i++) {
		float4 sample = tex2D(colorSampler, input.Texture + (float2(0, i - 2) / fbDim));
		total += filter[i] * sample;
	}
	return total;
}

float4 PSBlur(PSInput input) : COLOR0
{
	int i; // loop counter

	// the number of pixels around the center to sample
	float w = 1;
	// depth samples
	float4 samples[9]; // 9 local depth samples in row-major order
	samples[0] = tex2D(colorSampler, input.Texture + (float2(-w, -w) / fbDim));
	samples[1] = tex2D(colorSampler, input.Texture + (float2( 0, -w) / fbDim));
	samples[2] = tex2D(colorSampler, input.Texture + (float2( w, -w) / fbDim));
	samples[3] = tex2D(colorSampler, input.Texture + (float2(-w,  0) / fbDim));
	samples[4] = tex2D(colorSampler, input.Texture + (float2( 0,  0) / fbDim));
	samples[5] = tex2D(colorSampler, input.Texture + (float2( w,  0) / fbDim));
	samples[6] = tex2D(colorSampler, input.Texture + (float2(-w,  w) / fbDim));
	samples[7] = tex2D(colorSampler, input.Texture + (float2( 0,  w) / fbDim));
	samples[8] = tex2D(colorSampler, input.Texture + (float2( w,  w) / fbDim));

	float filter[9] = {
		1/9.0f, 1/9.0f, 1/9.0f,
		1/9.0f, 1/9.0f, 1/9.0f,
		1/9.0f, 1/9.0f, 1/9.0f
	};
	float4 total = float4(0, 0, 0, 0);
	for(i = 0; i < 9; i++) {
		total += filter[i] * samples[i];
	}

	return total;
}

float4 PSBasic(PSInput input) : COLOR0
{
	float4 color = tex2D(colorSampler, input.Texture);
	return color;
}

technique Ambient
{
	pass Ambient
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSAmbient();
	}
}
technique Directional
{
	pass Directional
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSDirectional();
	}
}
technique Point
{
	pass Point
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSPoint();
	}
}
technique Spot
{
	pass Spot
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSSpot();
	}
}
technique Fog
{
	pass Fog
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSFog();
	}
}
technique Laplace
{
	pass Laplace
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSLaplace();
	}
}
technique Denoise
{
	pass Denoise
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSDenoise();
	}
}
technique Bright
{
	pass Bright
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSBright();
	}
}
technique Blur
{
	pass Blur
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSBlur();
	}
}
technique BlurH
{
	pass BlurH
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSBlurH();
	}
}
technique BlurV
{
	pass BlurV
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSBlurV();
	}
}
technique Basic
{
	pass Basic
	{
		VertexShader = compile vs_3_0 VSFunction();
		PixelShader = compile ps_3_0 PSBasic();
	}
}